import socket
import select
import traceback

import config
import myhttp
import queue
import pipe
import threadpool, threading

task_queue = queue.Queue(config.task_queue_size)
workers = threadpool.ThreadPool(config.task_workers)


def process_request():
    import handler
    print("The worker started already.")
    while True:
        client, request = task_queue.get(block=True)
        try:
            response = handler.handle(client, request)
            client.putResponseInQueue(response)
        except:
            traceback.print_exc()
            # TODO: add code to process exceptions.


def start_workers():
    for i in range(config.task_workers):
        work_request = threadpool.WorkRequest(callable_=process_request)
        workers.putRequest(work_request)
    workers.wait()


task = threading.Thread(target=start_workers)
task.setDaemon(True)
task.start()


class Client:
    clients = dict()

    @classmethod
    def get_client_by_fd(cls, fd):
        return cls.clients[fd]

    def __init__(self, sock):
        self.sock = sock
        self.clients[sock.fileno()] = self
        self.send_queue = queue.Queue(10)

    def close(self):
        del self.clients[self.sock.fileno()]
        self.sock.close()

    def getRequest(self):
        request = myhttp.deserialize2(self.sock)
        return request

    def putResponseInQueue(self, response):
        self.send_queue.put(response)
        pipe.register_event(self.sock.fileno(), pipe.EV_WRITE)

    def getResponse(self):
        try:
            return self.send_queue.get_nowait()
        except queue.Empty:
            return None

_read_fds = []
_write_fds = []


def start():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    if config.sndbuf_size:
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, config.sndbuf_size)
    if config.rcvbuf_size:
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, config.rcvbuf_size)
    if config.snd_timeout:
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDTIMEO, config.snd_timeout)
    if config.rcv_timeout:
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, config.rcv_timeout)

    sock.bind((config.address, config.port))
    sock.listen(config.backlog)

    _read_fds.append(sock.fileno())
    _read_fds.append(pipe.get_reader_fd())

    while True:
        rds, wds, _ = select.select(_read_fds, _write_fds, (), None)
        if rds:
            for fd in rds:
                if fd == sock.fileno():
                    csock, _ = sock.accept()
                    _read_fds.append(csock.fileno())
                    Client(csock)
                elif fd == pipe.get_reader_fd():
                    cmd, fd, event = pipe.get_event()

                    if cmd == pipe.CMD_REGISTER:
                        if event & pipe.EV_READ and fd not in _read_fds:
                            _read_fds.append(fd)
                        if event & pipe.EV_WRITE and fd not in _write_fds:
                            _write_fds.append(fd)
                    elif cmd == pipe.CMD_UNREGISTER:
                        if event & pipe.EV_READ and fd in _read_fds:
                            _read_fds.remove(fd)
                        if event & pipe.EV_WRITE and fd in _write_fds:
                            _write_fds.remove(fd)
                else:
                    client = Client.get_client_by_fd(fd)
                    try:
                        request = client.getRequest()
                        task_queue.put((client, request), block=False)
                    except:
                        traceback.print_exc()
                        if fd in _read_fds: _read_fds.remove(fd)
                        if fd in _write_fds: _write_fds.remove(fd)
                        client.close()
        if wds:
            for fd in wds:
                client = Client.get_client_by_fd(fd)
                assert isinstance(client.send_queue, queue.Queue)

                resp = client.getResponse()
                if resp is None:
                    _write_fds.remove(client.sock.fileno())
                    _read_fds.remove(client.sock.fileno())
                    client.close()
                else:
                    print("SENT: \n" + myhttp.serialize(resp))
                    client.sock.send(myhttp.serialize(resp).encode("utf-8"))


if __name__ == "__main__":
    start()
